package main

import (
	"fmt"
	"log"
	"strings"
	"unicode"
	"unicode/utf8"
)

type item struct {
	typ     itemType // The type of this item.
	pos     int      // The starting position, in bytes, of this item in the input string.
	linepos int
	line    int
	val     string // The value of this item.
}

func init() {
	log.SetFlags(log.Lshortfile)
}

const eof = -1

func (i item) String() string {
	return fmt.Sprintf("%-13s%d:%d\t%q", itemStringMap[i.typ], i.line, i.linepos, i.val)
}

type itemType int

const (
	itemError itemType = iota
	itemEOF
	itemID
	itemSpace
	itemWrap
	itemTab
	itemNumber
	itemKeyword
	itemIF
)

var itemStringMap = map[itemType]string{
	itemError:   "itemError",
	itemEOF:     "itemEOF",
	itemID:      "itemID",
	itemSpace:   "itemSpace",
	itemWrap:    "itemWrap",
	itemTab:     "itemTab",
	itemNumber:  "itemNumber",
	itemKeyword: "itemKeyword",
	itemIF:      "itemIF",
}

var keyword = map[string]itemType{
	"if": itemIF,
}

func (e itemType) String() string {
	v, ok := itemStringMap[e]
	if !ok {
		panic("")
	}
	return v
}

type stateFn func(*lexer) stateFn
type lexer struct {
	name       string    // the name of the input; used only for error reports
	input      string    // the string being scanned
	state      stateFn   // the next lexing function to enter
	pos        int       // current position in the input
	start      int       // start position of this item
	width      int       // width of last rune read from input
	lastPos    int       // position of most recent item returned by nextItem
	items      chan item // channel of scanned items
	parenDepth int       // nesting depth of ( ) exprs
}

// next returns the next rune in the input.
func (l *lexer) next() rune {
	if int(l.pos) >= len(l.input) {
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
	l.width = w
	l.pos += l.width
	return r
}

// peek returns but does not consume the next rune in the input.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// backup steps back one rune. Can only be called once per call of next.
func (l *lexer) backup() {
	l.pos -= l.width
}

// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
	line, linepos := l.lineNumber()
	l.items <- item{
		typ:     t,
		pos:     l.start,
		val:     l.input[l.start:l.pos],
		linepos: linepos,
		line:    line,
	}
	l.start = l.pos
}

// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
	l.start = l.pos
}

// accept consumes the next rune if it's from the valid set.
func (l *lexer) accept(valid string) bool {
	if strings.IndexRune(valid, l.next()) >= 0 {
		return true
	}
	l.backup()
	return false
}

// acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptRun(valid string) {
	for strings.IndexRune(valid, l.next()) >= 0 {
	}
	l.backup()
}

// lineNumber reports which line we're on, based on the position of
// the previous item returned by nextItem. Doing it this way
// means we don't have to worry about peek double counting.
func (l *lexer) lineNumber() (line, linepos int) {
	line = 1 + strings.Count(l.input[:l.start], "\n")
	p := strings.LastIndex(l.input[:l.start], "\n")
	if p == -1 {
		linepos = l.start + 1
	} else {
		linepos = l.start - p
	}
	return
}

// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {

	line, linepos := l.lineNumber()
	l.items <- item{
		typ:     itemError,
		pos:     l.start,
		val:     fmt.Sprintf(format, args...),
		linepos: linepos,
		line:    line,
	}
	return nil
}

// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
	item := <-l.items
	l.lastPos = item.pos
	return item
}

// lex creates a new scanner for the input string.
func lex(name, input string) *lexer {

	l := &lexer{
		name:  name,
		input: input,
		items: make(chan item),
	}
	go l.run()
	return l
}

// run runs the state machine for the lexer.
func (l *lexer) run() {
	for l.state = lexText; l.state != nil; {
		l.state = l.state(l)
	}
}

// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
func isAlphaNumeric(r rune) bool {
	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}

func isAlpha(r rune) bool {
	return r == '_' || unicode.IsLetter(r)
}

func lexID(l *lexer) stateFn {
	var r rune
	r = l.next()
	if !isAlpha(r) {
		l.backup()
		return l.errorf("error lexID %q", r)
	}
	for {
		r = l.next()
		if !isAlphaNumeric(r) {
			l.backup()
			word := l.input[l.start:l.pos]
			if v, ok := keyword[word]; ok {
				l.emit(v)
			}
			l.emit(itemID)
			break
		}
	}
	return lexText
}

func lexSpace(l *lexer) stateFn {
	space := l.peek()
	var r rune
	for {
		r = l.next()
		if r != space {
			l.backup()
			break
		}
	}
	switch space {
	case '\t':
		l.emit(itemTab)
	case ' ':
		l.emit(itemSpace)
	case '\n':
		l.emit(itemWrap)
	}
	return lexText
}

func lexNumber(l *lexer) stateFn {
	var r rune
	for {
		r = l.next()
		if !unicode.IsNumber(r) {
			l.backup()
			break
		}
	}
	l.emit(itemNumber)
	return lexText
}

// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
	first := l.peek()
	switch {
	case first == -1:
		l.emit(itemEOF)
		return nil
	case isAlpha(first):
		return lexID(l)
	case unicode.IsDigit(first):
		return lexNumber(l)
	case first == ' ' || first == '\t' || first == '\n':
		return lexSpace(l)
	default:
		l.errorf("unknow lex %c", first)
	}
	return lexText
}

func main() {
	// l := lex("testlex", `abc if 123   azz
	// 	tt
	// `)
	l := lex("testlex", `ifx if iif       if
iff     if`)

	c := 0
	for {
		c++
		if c > 30 {
			log.Println("to much")
			return
		}
		item := l.nextItem()
		log.Println("item", item.String())
		if item.typ == itemEOF || item.typ == itemError {
			break
		}
	}
}
